package application;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.logging.Logger;

import javax.swing.AbstractListModel;

import net.jxta.discovery.DiscoveryEvent;
import net.jxta.discovery.DiscoveryListener;
import net.jxta.discovery.DiscoveryService;
import net.jxta.document.Advertisement;
import net.jxta.peergroup.PeerGroup;
import net.jxta.protocol.DiscoveryResponseMsg;
import net.jxta.protocol.PipeAdvertisement;
import servent.Servent;

/**
 * Illustrates the use of Discovery Service
 */
@SuppressWarnings("serial")
public class MachineIDProvider extends AbstractListModel implements DiscoveryListener {

    private transient DiscoveryService discovery;
	private boolean discover;
	private ArrayList<MachineID> elements;
	private int updateRequests;
	private boolean updateList;
    private long waittime = 60 * 1000L;
    private PeerGroup pGroup;
    private PipeAdvertisement pAdvert;

    private static MachineIDProvider instance;
    
    public static MachineIDProvider init( DiscoveryService discovery , PeerGroup peerGroup , PipeAdvertisement thisMachinesPipeAdv ){
    	instance = new MachineIDProvider(discovery, peerGroup, thisMachinesPipeAdv);
    	instance.startThreads();
    	return instance;
    }
    
    public static MachineIDProvider getInstance(){
    	return instance;
    }
    
    /**
     * Constructor for the DiscoveryClient
     */
    private MachineIDProvider( DiscoveryService discovery , PeerGroup peerGroup , PipeAdvertisement thisMachinesPipeAdv ) {
        this.discovery = discovery;
        discover = true;
        updateList = true;
        elements = new ArrayList<MachineID>();
        updateRequests = 0;
        pGroup = peerGroup;
        pAdvert = thisMachinesPipeAdv;
    }

    public void startThreads(){
    	Thread t1 = new Thread( new UpdateAdvertCacheDelegate(this), "UpdateAdvertCacheDelegate.run" );
    	Thread t2 = new Thread( new UpdateAdvertListDelegate(this), "UpdateAdvertListDelegate.run");
    	t1.start();
    	t2.start();
    }
    

    /**
     * loop forever attempting to discover advertisements every minute
     */
    public void startDiscovering() {
    	try {
    		// Add ourselves as a DiscoveryListener for DiscoveryResponse events
    		discovery.addDiscoveryListener(this);
    		while (discover) {

    			System.out.println("Sending a Discovery Message");
    			// look for any peer
    			discovery.getRemoteAdvertisements(
    					// no specific peer (propagate)
    					null,
    					//Adv type
    					DiscoveryService.ADV,
    					//Attribute = name
    					"Name",
    					//Value = the tutorial
    					Servent.SERVENT_ADVERTISSMENT_NAME,
    					100,
    					// no query specific listener. we are using a global listener
    					null);
    			Thread.sleep(waittime);
    		}
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    }

    /**
     * This method is called whenever a discovery response is received, which are
     * either in response to a query we sent, or a remote publish by another node
     *
     * @param ev the discovery event
     */
    public void discoveryEvent(DiscoveryEvent ev) {
    	DiscoveryResponseMsg res = ev.getResponse();
    	// let's get the responding peer's advertisement
    	System.out.println(" [ Got a Discovery Response ["
    			+ res.getResponseCount() + " elements] from peer : " + ev.getSource() + " ]");
    	Advertisement adv;
    	Enumeration<Advertisement> en = res.getAdvertisements();
    	if (en != null) {
            while (en.hasMoreElements()) {
                adv = (Advertisement) en.nextElement();
                System.out.println(adv);
            }
        }
        requestUpdateAdvertList();
    }

    private void requestUpdateAdvertList() {
    	updateRequests ++;
	}


	public void startUpdatingList() {

		while( updateList ){
			if( updateRequests > 0 ){
				elements.clear();
				int oldSize = elements.size();
				fireIntervalRemoved( this, 0, oldSize );
				try {
					Enumeration<Advertisement> advs = 
						discovery.getLocalAdvertisements(DiscoveryService.ADV , "Name", Servent.SERVENT_ADVERTISSMENT_NAME );
					while (advs.hasMoreElements()) {
						Advertisement adv = advs.nextElement();
						if( adv instanceof PipeAdvertisement ){
							MachineID mid = new MachineID(pGroup, (PipeAdvertisement) adv);
							elements.add(mid);
						}
						else{
							Logger.getLogger(Server.loggerName).severe("Advertisement is not a pipe advert instance :( -- get working");
						}
					}
					fireContentsChanged(this, 0, elements.size());
				} catch (IOException e) {
					e.printStackTrace();
				}			
				updateRequests = 0;
			}
			try {
				Thread.sleep(waittime/60);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}


	public void stop() {
    	discover = false;
    	updateList = false;
    }

	@Override
	public Object getElementAt(int index) {
		return elements.get(index);
	}

	@Override
	public int getSize() {
		return elements.size();
	}

	private class UpdateAdvertListDelegate implements Runnable{


		MachineIDProvider provider;

		public UpdateAdvertListDelegate(MachineIDProvider machineIDProvider ){
			provider = machineIDProvider;
		}

		@Override
		public void run() {
			provider.startUpdatingList();
		}
	}

	private class UpdateAdvertCacheDelegate implements Runnable{

		MachineIDProvider provider ;
		UpdateAdvertCacheDelegate( MachineIDProvider provider ){
			this.provider = provider;
		}

		@Override
		public void run() {
			provider.startDiscovering();
		}
	}

	public Collection<MachineID> getAvailableMachines() {
		//Filter the adverts and remove the locally produced adverts.
		//we don't want to connect to ourselves
		Collection<MachineID> col = new LinkedList<MachineID>();
		for (MachineID machineID : elements) {
			if( !arePipeAdvsEqual(machineID.getpAdv(), pAdvert )){
				col.add( machineID );
			}
		}
		return col;
	}
	
	public static boolean arePipeAdvsEqual( PipeAdvertisement padv1, PipeAdvertisement padv2){
		
        if (padv1 == padv2) {
            return true;
        }


            PipeAdvertisement likeMe = (PipeAdvertisement) padv2;

            if (!padv1.getPipeID().equals(likeMe.getPipeID())) {
                return false;
            }

            if (!padv1.getType().equals(likeMe.getType())) {
                return false;
            }

            String pipeName = padv1.getName();

            if (pipeName == null ? likeMe.getName() != null : !pipeName.equals(likeMe.getName())) {
                return false;
            }

            String pipeDescription = padv1.getDescription();

            return !(pipeDescription == null ? likeMe.getDescription() != null : !pipeDescription.equals(likeMe.getDescription()));

	}

	public MachineID getMachineID(String peerName ) {
		for (MachineID mid : elements) {
			if( mid.getPeerName().equals(peerName)){
				return mid;
			}
		}
		return null;
	}
}

